---@class CronMgr
CronMgr = {
    cacheList = {}
}
local this = CronMgr
local FormatType = {
    single = 1,
    multi = 2,
}
---获取cron基于timeStampSecond时间的下一个开启时间
---@param timeStampSecond number 时间戳(秒)
function CronMgr.GetNextDateTime(cronStr,timeStampSecond)
    if cronStr == nil then return 0 end
    local cache = this.cacheList[cronStr]

    if cache and cache.refer <= timeStampSecond and cache.cron > timeStampSecond then
        return cache.cron
    end
    timeStampSecond = math.floor(timeStampSecond)
    local old = timeStampSecond
    local arr = string.split(cronStr," ")
    if this.IsFixTime(cronStr) then
        local t = os.time({sec = arr[1],min = arr[2],hour = arr[3],year = arr[7],month = arr[5],day=arr[4]})
        this.cacheList[cronStr] = { cron= t,refer = timeStampSecond }
        return t
    end
    local S,M,H,m,Y = arr[1],arr[2],arr[3],arr[5],arr[7]
    this.ClearCron()
    this.InitSecond(S)
    this.InitMinutes(M)
    this.InitHours(H)
    this.InitMonth(m)
    this.InitYear(Y)
    local addTime = 0
    local cron = this.cron
    local nowTB = {}
    local firstYear = 0
    local isDayIn = false
    local oldDay = -1
    local loopCount = 0
    while(true) do
        loopCount = loopCount + 1
        if loopCount > 100 then
            logError(cronStr..","..old.." GetNextDateTime 异常退出")
            return timeStampSecond
        end
        local nowStr = os.date("%Y:%m:%d:%H:%M:%S:%w",timeStampSecond)
        table.clear(nowTB)
        string_split(nowStr,":",nowTB)
        local week = tonumber(nowTB[7])
        local second = tonumber(nowTB[6])
        local minute = tonumber(nowTB[5])
        local hour = tonumber(nowTB[4])
        local day = tonumber(nowTB[3])
        local month = tonumber(nowTB[2])
        local year = tonumber(nowTB[1])
        local flag1 = this.isSecondAll or cron.seconds[second]
        local flag2 = this.isMinutesAll or cron.minutes[minute]
        local flag3 = this.isHourAll or cron.hours[hour]
        local flag4 = this.isMonthAll or cron.month[month]
        local flag5 = this.isYearAll or cron.year[year]
        if oldDay ~= day then
            isDayIn = false
        end
        if flag1 and flag2 and flag3 and flag4 and flag5 and isDayIn then
            this.cacheList[cronStr] = { cron= timeStampSecond,refer = old }
            return timeStampSecond
        end
        if firstYear == 0 then
            firstYear = year
        else
            if year - firstYear > 2 then--跨度太长不做处理，结束循环
                return 0
            end
        end
        local isHandled = false
        if not isDayIn then
            if arr[4] ~= "?" then
                this.InitDays(arr[4],31)
                if this.isDayAll or cron.days[day] then
                    isDayIn = true
                    oldDay = day
                else
                    isHandled = true
                    addTime = 3600*24 - hour*3600 - minute*60 - second
                    this.ClearMonth()
                    this.ClearWeek()
                end
            else
                this.InitWeek(arr[6])
                if week == 0 then
                    week = 7
                end
                if this.isWeekAll or cron.weeks[week] then
                    isDayIn = true
                    oldDay = day
                else
                    isHandled = true
                    addTime = 3600*24 - hour*3600 - minute*60 - second
                    this.ClearMonth()
                    this.ClearWeek()
                end
            end
        end
        if not isHandled then
            isHandled,addTime = this.HandleSecond(tonumber(S),second)
        end
        if not isHandled then
            isHandled,addTime = this.HandleMinute(tonumber(M),minute)
        end
        if not isHandled then
            isHandled,addTime = this.HandleHour(tonumber(H),hour)
        end
        timeStampSecond = timeStampSecond + addTime
    end
    return 0
end
---@private
function CronMgr.HandleSecond(cronSecond,nowSecond)
    local addTime = 0
    local isHandled = false
    if this.cron.seconds[nowSecond] then
        return isHandled ,addTime
    end
    if this.sFormatType == FormatType.single then
        if cronSecond == 0 or cronSecond < nowSecond then
            addTime = 60-nowSecond
        else
            addTime = cronSecond - nowSecond
        end
        isHandled = true
    elseif not this.isSecondAll then
        local index = nowSecond
        local max = 60
        while(index < max) do
            if this.cron.seconds[index] then
                break
            end
            addTime = addTime + 1
            index = index + 1
        end
        isHandled = true
    end
    return isHandled ,addTime
end
---@private
function CronMgr.HandleMinute(cronMinute,nowMinute)
    local addTime = 0
    local isHandled = false
    if this.cron.minutes[nowMinute] then
        return isHandled,addTime
    end
    if this.mFormatType == FormatType.single then
        if cronMinute == 0 or cronMinute < nowMinute then
            addTime = (60-nowMinute) * 60
        else
            addTime = (cronMinute - nowMinute)*60
        end
        isHandled = true
    elseif not this.isMinutesAll then
        local index = nowMinute
        local max = 60
        while(index < max) do
            if this.cron.minutes[index] then
                break
            end
            addTime = addTime + 60
            index = index + 1
        end
        isHandled = true
    end
    return isHandled ,addTime
end
---@private
function CronMgr.HandleHour(cronHour,nowHour)
    local addTime = 0
    local isHandled = false
    if this.cron.hours[nowHour] then
        return isHandled,addTime
    end
    if this.hFormatType == FormatType.single then
        if cronHour == 0 or cronHour < nowHour then
            addTime = (24-nowHour) * 3600
        else
            addTime = (cronHour - nowHour) * 3600
        end
        isHandled = true
    elseif not this.isSecondAll then
        local index = nowHour
        local max = 24
        while(index < max) do
            if this.cron.hours[index] then
                break
            end
            addTime = addTime + 3600
            index = index + 1
        end
        isHandled = true
    end
    return isHandled ,addTime
end
---@private
function CronMgr.IsFixTime(cronStr)
    if string.find(cronStr,"-") or string.find(cronStr,",") or string.find(cronStr,"/")
        or string.find(cronStr,"%*") then
        return false
    end
    return true
end
---@private
function CronMgr.InitSecond(str)
    this.isSecondAll = false
    this.sFormatType = FormatType.multi
    if str == "*" then
        this.isSecondAll = true
        return
    end
    local tb = this.cron.seconds
    this.sFormatType = this.ParseCommon(tb,str,60)
end
---@private
function CronMgr.InitMinutes(str)
    this.isMinutesAll = false
    this.mFormatType = FormatType.multi
    if str == "*" then
        this.isMinutesAll = true
        return
    end
    local tb = this.cron.minutes
    this.mFormatType = this.ParseCommon(tb,str,60)
end
---@private
function CronMgr.InitHours(str)
    this.isHourAll = false
    this.hFormatType = FormatType.multi
    if str == "*" then
        this.isHourAll = true
        return
    end
    local tb = this.cron.hours
    this.hFormatType = this.ParseCommon(tb,str,24)
end
---@private
function CronMgr.InitDays(str,len)
    this.isWeekAll = true
    this.isDayAll = false
    if str == "*" or str == "?" then
        this.isDayAll = true
        return
    end
    local tb = this.cron.days
    this.ParseCommon(tb,str,len)
end
---@private
function CronMgr.InitWeek(str)
    this.isWeekAll = false
    if str == "*" or str == "?" then
        this.isWeekAll = true
        return
    end
    local tb = this.cron.weeks
    this.ParseCommon(tb,str,7)
    this.isDayAll = true
end
---@private
function CronMgr.InitMonth(str)
    this.isMonthAll = false
    if str == "*" then
        this.isMonthAll = true
        return
    end
    local tb = this.cron.month
    this.ParseCommon(tb,str,12)
end
---@private
function CronMgr.InitYear(str)
    this.isYearAll = false
    if str == "*" or str == nil or str == "" then
        this.isYearAll = true
        return
    end
    local tb = this.cron.year
    this.ParseCommon(tb,str,5)
end
---@private
function CronMgr.ParseCommon(tb,str,limit)
    local _type = FormatType.multi
    if string.find(str,"-") then
        this.ParseRange(tb,str)
        return _type
    end
    if string.find(str,"/") then
        this.ParseSlash(tb,str,limit)
        return _type
    end
    if string.find(str,",") then
        this.ParseComma(tb,str)
        return _type
    end
    tb[tonumber(str)] = true
    _type = FormatType.single
    return _type
end
---@private
function CronMgr.ParseRange(tb,str)
    local arr = string.split(str,"-")
    local begin = tonumber(arr[1])
    local _end = tonumber(arr[2])
    for i = begin,_end do
        tb[i] = true
    end
end
---@private
function CronMgr.ParseSlash(tb,str,limit)
    local arr = string.split(str,"/")
    local begin = tonumber(arr[1])
    local interval = tonumber(arr[2])
    while true do
        tb[begin] = true
        if begin + interval >= limit then
            break
        end
        begin = begin + interval;
    end
end
---@private
function CronMgr.ParseComma(tb,str)
    local arr = string.split(str,",")
    for k,v in pairs(arr) do
        tb[tonumber(arr[k])] = true
    end
end
---@private
function CronMgr.ClearCron()
    if not this.cron then
        this.cron = {}
    end
    this.cron.seconds = {}
    this.cron.minutes = {}
    this.cron.hours = {}
    this.cron.days = {}
    this.cron.month = {}
    this.cron.weeks = {}
    this.cron.year = {}
end
---@private
function CronMgr.ClearWeek()
    table.clear(this.cron.weeks)
end
---@private
function CronMgr.ClearMonth()
    table.clear(this.cron.month)
end
return CronMgr